﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using SharpSvn;

namespace EyeballLib
{
    class SvnDownloader
    {
        private SvnClient Client { get; set; }
        public long Revision { get; set; }
        public Repository RepositoryInfo { get; set; }
        public DataClassesDataContext DataSource { get; set; }

        public SvnDownloader(DataClassesDataContext dataSource, Repository repository, long revision)
        {
            DataSource = dataSource;
            Client = new SvnClient();
            Revision = revision;
            RepositoryInfo = repository;
        }

        private Developer CreateDeveloper(string developerName)
        {
            lock (DataSource)
            {
                var developer = RepositoryInfo.Developers.SingleOrDefault(d => d.Name == developerName) ??
                                new Developer { Name = developerName, Repository = RepositoryInfo.Name, Repository1 = RepositoryInfo };
                return developer;
            }
        }

        private Commit CreateCommit(DateTime commitTime, Developer developer)
        {
            lock (DataSource)
            {
                var commit = RepositoryInfo.Commits.SingleOrDefault(c => c.Id == Revision);

                if (commit != null)
                {
                    if (commit.Complete)
                        return commit;

                    RepositoryInfo.Commits.Remove(commit);
                }

                commit = new Commit
                             {
                                 Developer = developer.Name,
                                 Id = Revision,
                                 Repository = RepositoryInfo.Name,
                                 Repository1 = RepositoryInfo,
                                 Developer1 = developer,
                                 Time = commitTime
                             };
                return commit;
            }
        }

        private File CreateFile(Uri fileLocation)
        {
            lock (DataSource)
            {
                var file = RepositoryInfo.Files.SingleOrDefault(f => f.Location == fileLocation.ToString());
                if (file == null)
                {
                    file = new File
                    {
                        Location = fileLocation.ToString(),
                        Repository = RepositoryInfo.Name,
                        Repository1 = RepositoryInfo
                    };
                    RepositoryInfo.Files.Add(file);
                }
                return file;
            }
        }

        private FileChange CreateFileChange(File file, Commit commit)
        {
            lock (DataSource)
            {
                var fileChange = new FileChange
                {
                    File1 = file,
                    Commit = commit.Id,
                    Commit1 = commit,
                    File = file.Location,
                    Repository = RepositoryInfo.Name
                };
                return fileChange;
            }
        }

        private bool CreateLines(FileChange fileChange, SvnChangeItem item)
        {
            using (var stream = new MemoryStream())
            {
                try
                {
                    switch (item.Action)
                    {
                        case SvnChangeAction.Replace:
                            Client.Write(new SvnUriTarget(fileChange.File1.Location, fileChange.Commit1.Id), stream);
                            CountLines(fileChange, stream, LineType.Addition);
                            stream.Dispose();
                            Client.Write(new SvnUriTarget(fileChange.File1.Location, fileChange.Commit1.Id - 1), stream);
                            CountLines(fileChange, stream, LineType.Removal);
                            break;
                        case SvnChangeAction.Add:
                            Client.Write(new SvnUriTarget(fileChange.File1.Location, fileChange.Commit1.Id), stream);
                            CountLines(fileChange, stream, LineType.Addition);
                            break;
                        case SvnChangeAction.Delete:
                            Client.Write(new SvnUriTarget(fileChange.File1.Location, fileChange.Commit1.Id - 1), stream);
                            CountLines(fileChange, stream, LineType.Removal);
                            break;
                        default:
                            Client.Diff(new SvnUriTarget(fileChange.File1.Location), new SvnRevisionRange(fileChange.Commit1.Id - 1, fileChange.Commit1.Id), stream);
                            CountLines(fileChange, stream);
                            break;
                    }
                }
                catch (SvnClientNodeKindException) { return false; }
                catch (SvnRepositoryIOException)
                {
                    try
                    {
                        Client.Write(new SvnUriTarget(fileChange.File1.Location, fileChange.Commit1.Id - 1), stream);
                        CountLines(fileChange, stream, LineType.Removal);
                    }
                    catch
                    {
                        return false;
                    }
                }
                //catch (Exception) { continue; } //Catch errors where paths don't fit in the Uri class. Example: filename.
            }
            return true;
        }

        public bool CheckExtensions(Uri location)
        {
            if (!location.ToString().Contains("."))
                return true;
            var allowed = !RepositoryInfo.ExtensionsAllowed;
            if (RepositoryInfo.Extensions.Any(extension => location.ToString().EndsWith("." + extension.Extension1)))
            {
                allowed = !allowed;
            }
            return allowed;
        }

        public void FetchCommit()
        {
            var log = GetLog(Revision);

            if (log == null || log.ChangedPaths == null || log.Author == null)
                return;

            var developer = CreateDeveloper(log.Author);
            var commit = CreateCommit(log.Time, developer);
            if (commit.Complete) return;
            
            foreach (var fileChange in from item in log.ChangedPaths
                                       let location = new Uri(RepositoryInfo.RemoteLocation + item.RepositoryPath)
                                       where CheckPath(location) && CheckExtensions(location)
                                       let file = CreateFile(location)
                                       let fileChange = CreateFileChange(file, commit)
                                       let error = CreateLines(fileChange, item)
                                       where error
                                       select fileChange)
            {
                commit.FileChanges.Add(fileChange);
            }

            commit.Complete = true;

            lock (DataSource)
            {
                DataSource.SubmitChanges();
            }
        }

        private bool CheckPath(Uri location)
        {
            return !location.AbsolutePath.StartsWith(@"tags/");
        }

        private void CountLines(FileChange fileChange, Stream stream, LineType type)
        {
            using (var reader = new StreamReader(stream))
            {
                stream.Seek(0, SeekOrigin.Begin);
                string sline;
                while ((sline = reader.ReadLine()) != null)
                {
                    lock (DataSource)
                    {
                        var line = new Line
                                       {
                                           Commit = fileChange.Commit,
                                           File = fileChange.File,
                                           Nr = fileChange.Lines.Count,
                                           FileChange = fileChange,
                                           Content = sline,
                                           Type = type.ToString()
                                       };
                        fileChange.Lines.Add(line);
                    }
                }
            }
        }

        private void CountLines(FileChange fileChange, Stream stream)
        {
            var plus = new Regex(@"^\+(.*)");
            var minus = new Regex(@"^\-(.*)");
            var atat = new Regex(@"@@\s(.+)\s@@");
            using (var reader = new StreamReader(stream))
            {
                stream.Seek(0, SeekOrigin.Begin);
                string sline;
                var startcount = false;
                while ((sline = reader.ReadLine()) != null)
                {
                    if (atat.Match(sline).Success)
                    {
                        startcount = true;
                        continue;
                    }

                    if (minus.Match(sline).Success && startcount)
                    {
                        lock (DataSource)
                        {
                            var line = new Line
                                           {
                                               Commit = fileChange.Commit,
                                               File = fileChange.File,
                                               Nr = fileChange.Lines.Count,
                                               FileChange = fileChange,
                                               Content = sline,
                                               Type = LineType.Removal.ToString(),
                                               Repository = RepositoryInfo.Name
                                           };
                            fileChange.Lines.Add(line);
                        }
                    }
                    if (plus.Match(sline).Success && startcount)
                    {
                        lock (DataSource)
                        {
                            var line = new Line
                                           {
                                               Commit = fileChange.Commit,
                                               File = fileChange.File,
                                               Nr = fileChange.Lines.Count,
                                               FileChange = fileChange,
                                               Content = sline,
                                               Type = LineType.Addition.ToString(),
                                               Repository = RepositoryInfo.Name
                                           };
                            fileChange.Lines.Add(line);
                        }
                    }
                }
            }
        }

        private SvnLogEventArgs GetLog(long revision)
        {
            var arguments = new SvnLogArgs(new SvnRevisionRange(revision, revision));

            Collection<SvnLogEventArgs> collection;
            var log = Client.GetLog(new Uri(RepositoryInfo.RemoteLocation), arguments, out collection);

            if (collection.Count == 1 && log)
                return collection[0];
            return null;
        }
    }
}
